using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Specialized;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
using System.Globalization;

using MSHelpLib;
using MSHelpLib.BaseCode;

namespace MSHelpLib.HXS.Decoding
{
	/// <summary>
	/// Internal enumeration for specifying the type of the html-help file
	/// </summary>
	internal enum HXSFileType
	{
		/// <summary>
		/// HXS - compiled contents file
		/// </summary>
		/// <remarks>A file with this extension must always exist. If the file would be too long, some parts 
		/// can be splitted into the filestypes below.</remarks>
		HXS = 0,
		/// <summary>
		/// CHI - compiled system file
		/// </summary>
		HXI = 1,
	}

    /// <summary>
    /// The class <c>HXSFile</c> implemts methods and properties for handling a single hxsfile.
    /// </summary>
	public sealed class HXSFile : BaseHelpFile, IDisposable, IHelpFile
	{
		/// <summary>
		/// Internal member storing the full filename
		/// </summary>
		private string _hxsFileName = "";
		/// <summary>
		/// Internal member storing the full filename of the chi-file (includes all system files)
		/// The file name is zero-length if there is no chi-file
		/// </summary>
		private string _hxiFileName = "";
		
		/// <summary>
		/// Internal memebr storing the hxs file info
		/// </summary>
		private ChmFileInfo _hxsFileInfo = null;
		
		/// <summary>
		/// Internal member storing the dumping info instance
		/// </summary>		
		internal string HXSName="";

		private HXSStream _currentWrapper;
		private HXSStream _baseStream=null;
        /// <summary>
        /// Gets the base stream.
        /// </summary>
        /// <value>The base stream.</value>
		public override IHelpStream BaseStream
		{
			get 
			{ 
				if (_baseStream==null)
					_baseStream=new HXSStream(this.HXSFilePath,this);
				return (IHelpStream)_baseStream; 
			}
		}


        /// <summary>
        /// Initializes a new instance of the <see cref="HXSFile"/> class.
        /// </summary>
        /// <param name="systemInstance">The system instance.</param>
        /// <param name="hxsFile">The HXS file.</param>
		public HXSFile(IHelpSystem systemInstance, string hxsFile) : this(systemInstance, hxsFile, (int)(LoadFlags.LoadDumpFile|LoadFlags.SaveDumpFile|LoadFlags.CreateSearchIndexOnLoad))
		{
		}

        /// <summary>
        /// Initializes a new instance of the <see cref="HXSFile"/> class.
        /// </summary>
        /// <param name="systemInstance">The system instance.</param>
        /// <param name="hxsFile">The HXS file.</param>
        /// <param name="SH">The SH.</param>
        /// <param name="PH">The PH.</param>
		public HXSFile(IHelpSystem systemInstance, string hxsFile, StatusHandler SH, ProgressHandler PH) : this(systemInstance,hxsFile, (int)(LoadFlags.LoadDumpFile|LoadFlags.SaveDumpFile|LoadFlags.CreateSearchIndexOnLoad),SH,PH)
		{
		}		

		/// <summary>
		/// Creates a new instance of the class
		/// </summary>
		/// <param name="systemInstance">a reference to the hosting HXSHelpSystem instance</param>
		/// <param name="hxsFile">hxs file to read</param>
		/// <param name="flags">A dumping info class</param>
		internal HXSFile(IHelpSystem systemInstance, string hxsFile, int flags)
			: this(systemInstance, hxsFile, flags, null,null)
		{
		}				
		
		internal HXSFile(IHelpSystem systemInstance, string hxsFile, int _flags, StatusHandler SH, ProgressHandler PH)
		{
			Open(systemInstance, hxsFile, _flags, SH, PH);
		}

        /// <summary>
        /// Opens the specified system instance.
        /// </summary>
        /// <param name="systemInstance">The system instance.</param>
        /// <param name="hxsFile">The HXS file.</param>
        /// <param name="_flags">The _flags.</param>
        /// <param name="SH">The SH.</param>
        /// <param name="PH">The PH.</param>
        /// <returns></returns>
		public override bool Open(IHelpSystem systemInstance, string hxsFile, int _flags, StatusHandler SH, ProgressHandler PH)
		{
			_urlPrefix = "ms-help:";

			bool rc=base.Open(systemInstance, hxsFile, _flags,SH, PH);
			if (rc==false)
				return false;
								
			HXSName=m_FInfo.Name;			
			OnStatus("Loading "+HXSName);	

			if ((!hxsFile.ToLower().EndsWith(".hxs")) && (!hxsFile.ToLower().EndsWith(".hxi")))
			{
				throw new ArgumentException("MSHelpLib file must have the extension .hxs or .hxi", "hxsFile");
			}

			_hxsFileName = m_FInfo.FullName;
			_hxiFileName = m_FInfo.FullName.ToLower();
			_hxiFileName=_hxiFileName.Replace(".hxs",".hxi");

			if ((Flags & (int)LoadFlags.LoadDumpFile)!=0)
			{
				OnStatus("Checking QuickLoad Data");
				if (this.LoadDump(this))
				{
					return true;
				}
			}

			// Read the IStorage file system
			if (File.Exists(_hxiFileName))
				ReadFile(_hxiFileName, HXSFileType.HXI);

			if( File.Exists(_hxsFileName) )
				ReadFile(_hxsFileName, HXSFileType.HXS);			

			// Compact Index
			if ((this.Index!=null) && (this.Index.KLinks!=null))
			{
				this.OnStatus("Compacting Index");
				MSHelper.CompactIndex(Index.KLinks);			
			}

			if ((Flags & (int)LoadFlags.CreateSearchIndexOnLoad)!=0)
				this.InitSearch();

			if ((Flags & (int)LoadFlags.SaveDumpFile)!=0)
			{
				OnStatus("Writing QuickLoad Data");
				SaveDump();
			}			

			return true;
		}		

		private Hashtable StringsHash=null;
        static int Count = 0;
		private bool ReadStrings()
		{
            Count++;			
			IFileInfo fileInfo=_currentWrapper.FindFile("/$INDEX/$Strings");
			if (fileInfo==null)
				return false;

			OnStatus("Extracting /STRINGS file");
			MemoryStream memStream=_currentWrapper.Extract(fileInfo);
			OnStatus("Processing /STRINGS file");
			StringsHash=new Hashtable();
			BinaryReaderEx binReader=null;
			try
			{			
				OnProgress(0);
				binReader=new BinaryReaderEx(memStream);
				UInt32 StartWord=binReader.ReadUInt32();
				Encoding encoder=new UTF8Encoding();                
				// Read Strings
				while (!binReader.EOF)
				{
					OnProgress(binReader.PercentageRead);					

					UInt32 offset=(UInt32)binReader.Position;
					byte peek = binReader.ReadByte();
					if (peek == 0xff)
					{
						int block = (int)((binReader.Position-1) >> 12);
						offset = (UInt32)((block + 1) * 0x1000);
						binReader.Seek(offset, SeekOrigin.Begin);
					}
					else
					{
						// Else Move back one char
						binReader.Seek( - 1, SeekOrigin.Current);
					}
					string StringEntry=binReader.ExtractUTF16String(0,true,encoder);					
					StringsHash.Add(offset,StringEntry);
				}				
			}
			catch(Exception ex)
			{
				Debug.WriteLine(ex.Message);
			}
			finally
			{
				binReader.Close();
				memStream.Close();
				memStream=null;
				OnProgress(0);
				OnStatus("Processing");
			}
			return true;
		}				

		private Hashtable URLStringsHash=null;
		private bool ReadURLStrings()
		{
			IFileInfo fileInfo=_currentWrapper.FindFile("/$INDEX/$URLSTR");
			if (fileInfo==null)
				return false;

			OnStatus("Extracting /URLSTR file");
			MemoryStream memStream=_currentWrapper.Extract(fileInfo);						
			OnStatus("Processing /URLSTR file");
			BinaryReaderEx binReader=null;
			URLStringsHash=new Hashtable();
			OnProgress(0);

			try
			{			
				binReader=new BinaryReaderEx(memStream);
				Encoding encoder=new UTF8Encoding();				

				// Word =0 ???
				binReader.ReadUInt16();

				// Read Strings
#if MoreInfo
				UInt32 Word1=0;
				UInt32 Word2=0;
				UInt32 Word3=0;
				UInt32 Word4=0;
				UInt32 Word5=0;
				UInt32 Word6=0;
#endif
				UInt32 EntryLen=0;

				while (!binReader.EOF)
				{                    
					OnProgress(binReader.PercentageRead);

					UInt16 peek = binReader.ReadUInt16();
					if (peek == 0xffff)
					{
						long Block = (binReader.Position-2) >> 12;
						binReader.Seek(((Block + 1) * 0x1000), SeekOrigin.Begin);
					}
					else
					{
						binReader.Seek(-2, SeekOrigin.Current);
					}

					UInt32 offset = (UInt32)binReader.Position;
					EntryLen=binReader.ReadUInt32();
					
#if MoreInfo
					Word1=binReader.ReadUInt16();	// 2 bytes
					Word2=binReader.ReadUInt16();	// 2 bytes
					Word3=binReader.ReadUInt32();	// 4 bytes
					Word4=binReader.ReadUInt16();	// 2 bytes
#else	
					binReader.Seek(10,SeekOrigin.Current);
#endif
					string URLString=binReader.ExtractUTF16String(0,true,encoder);
					// Trace.WriteLine(string.Format("{0} : {1} {2}",offset.ToString("x04"),EntryLen.ToString("x04"),URLString));

#if MoreInfo
					Word5=binReader.ReadUInt32();	// 4 bytes			
					Word6=binReader.ReadUInt16();	// 2 bytes			
#else	
					// binReader.Seek(6,SeekOrigin.Current);
#endif
					binReader.Seek(offset + EntryLen, SeekOrigin.Begin);

					URLStringsHash.Add(offset,URLString);
				}				
			}
			catch(Exception ex)
			{
				Debug.WriteLine(ex.Message);
			}
			finally
			{
				binReader.Close();
				memStream.Close();
				memStream=null;
				OnProgress(0);
				OnStatus("Processing");
			}

			return true;
		}

		private Hashtable TopicHash=null;
		private bool ReadTopics()
		{
			IFileInfo fileInfo=_currentWrapper.FindFile("/$INDEX/$TOPICS");
			if (fileInfo==null)
				return false;

			OnStatus("Extracting /TOPICS file");
			MemoryStream memStream=_currentWrapper.Extract(fileInfo);						
			OnStatus("Processing /TOPICS file");
			BinaryReaderEx binReader=null;
			TopicHash=new Hashtable();
			OnProgress(0);
			
			try
			{
				UInt32 Count=0;				
				binReader=new BinaryReaderEx(memStream);
								
				UInt32 Word1=0; // == Offset of string
				UInt32 Word2=0;	// == Offset of URLTable
#if MoreInfo
				UInt32 Word3=0;	// == Index Value
				UInt32 Word4=0;	// == ??
#endif
				while (!binReader.EOF)
				{								
					OnProgress(binReader.PercentageRead);

					Word1=binReader.ReadUInt32();	// 4 bytes
					Word2=binReader.ReadUInt32();	// 4 bytes
	
#if MoreInfo
					Word3=binReader.ReadUInt32();	// 4 bytes
					Word4=binReader.ReadUInt32();	// 4 bytes
#else
					binReader.Seek(8,SeekOrigin.Current);
#endif

					TopicHash.Add(Count,new HXTopic(Word1,Word2,StringsHash,URLStringsHash));
					Count++;
				}				
			}
			catch(Exception ex)
			{
				Debug.WriteLine(ex.Message);
			}
			finally
			{
				binReader.Close();
				memStream.Close();
				memStream=null;
				OnProgress(0);
				OnStatus("Processing");
			}
			return true;
		}

		private bool ReadBinaryIndex(IFileInfo fileInfo)
		{
			if (fileInfo==null)
				return false;

			OnStatus("Extracting Binary Index File");
			MemoryStream memStream=_currentWrapper.Extract(fileInfo);						
			OnStatus("Processing Binary Index File");
			BinaryReaderEx binReader=null;
			OnProgress(0);

			_indexKLinks=new ArrayList();
			IndexItem2 item=null;			
			Hashtable buildInfo=new Hashtable();
			try
			{
				Encoding encoder=new UTF8Encoding();				
				binReader=new BinaryReaderEx(memStream);
								
				UInt32 EntryLen=0; // == Len of entry
				byte   LevelCount=0;	// == Level Count
				byte   Flags= 0;	// == Flags 
				UInt32 NumTopics=0;	// == Number of UInt32's following last String
				// UInt32 Word4=0;	// == ??
				
				while(!binReader.EOF)
				{					
					OnProgress(binReader.PercentageRead);

					// Trace.WriteLine("0x" + binReader.Position.ToString("x04"));

					long EntryStartPos = binReader.Position;

					EntryLen=binReader.ReadUInt16();                    
					LevelCount=binReader.ReadByte();
					Flags = binReader.ReadByte();
					NumTopics= binReader.ReadUInt16();

					string NewString=binReader.ExtractUTF16String(0,true,encoder);
					// Trace.WriteLine(String.Format("{0} Level Count={1}, Flags={2}",NewString,LevelCount.ToString("x02"),Flags.ToString("x02")));
					
					if ((Flags & 0x04)==0x04)
					{
						if (LevelCount == 0)
						{                            
							item = new IndexItem2();
							item.Title = NewString;
							item.helpFile = this;
						}
						else
						{
							for (int level = 1; level <= LevelCount; level++)
							{
								// These entries seem to be fill child entrys of a parent entry
								string NewString2 = binReader.ExtractUTF16String(0, true, encoder);
								// Trace.Indent();
								// Trace.WriteLine(NewString2);

								item = new IndexItem2();
								item.Title = NewString2;
								item.helpFile = this;

								if (level != LevelCount)
								{
									if (buildInfo.ContainsKey(NewString + ":"))
									{
										IndexItem2 parent = (IndexItem2)buildInfo[NewString + ":"];
										parent.helpFile = this;

										bool bFound = false;
										foreach (IndexItem2 i in parent.Children)
										{
											if ((i.Title == item.Title) && (i.Url == item.Url))
											{
												bFound = true;
												break;
											}
										}
										if (!bFound)
											parent.Children.Add(item);
									}
									else
									{
										AddNewItem(item, buildInfo);
									}
								}
								// Trace.Unindent();
							}
						}

						for (int i = 0; i < NumTopics; i++)
						{
							UInt32 TopicID=binReader.ReadUInt32();                            
							if (TopicHash.ContainsKey(TopicID))
							{
								HXTopic t=(HXTopic)TopicHash[TopicID];							
																
								if ((StringsHash.ContainsKey(t.Word1)) && (URLStringsHash.ContainsKey(t.Word2)))
								{
									IndexItem2 subitem=new IndexItem2();
									subitem.helpFile = this;
									if (StringsHash.ContainsKey(t.Word1))
										subitem.Title=StringsHash[t.Word1].ToString();
									if (URLStringsHash.ContainsKey(t.Word2))
										subitem.Url=URLStringsHash[t.Word2].ToString();
		
									if (subitem.Title!="")
									{
										item.Children.Add(subitem);
										// Trace.Indent();
										// Trace.WriteLine(string.Format("{0} - {1}",subitem.Title,subitem.Url));
										// Trace.Unindent();
									}
								}
							}							
						}						
						
						if (item.Children.Count==0)
							item=null;
						else if (buildInfo.ContainsKey(NewString+":"))
						{
							IndexItem2 parent=(IndexItem2)buildInfo[NewString+":"];
							parent.helpFile=this;

							bool bFound=false;
							foreach(IndexItem2 i in parent.Children)
							{
								if ((i.Title==item.Title) && (i.Url==item.Url))
								{
									bFound=true;
									break;
								}
							}
							if (!bFound)
								parent.Children.Add(item);
							item=null;
						}
					}
					else
					{	
						// Parent Item
						item=new IndexItem2();
						item.Title=NewString;
						item.helpFile=this;
					}

					AddNewItem(item, buildInfo);
					item = null;

					// Seek to start of next entry - Should stop mis-reading of file format
					if ((Flags & 0x1) == 0x01)
					{
						long block=(EntryStartPos + EntryLen)>>12;
						binReader.Seek(((block+1)*0x1000), SeekOrigin.Begin);
					}
					else
						binReader.Seek(EntryStartPos+EntryLen, SeekOrigin.Begin);
				}
			}
			catch(Exception ex)
			{
				Debug.WriteLine(ex.Message);
			}
			finally
			{
				binReader.Close();
				memStream.Close();
				memStream=null;
				OnProgress(0);
				OnStatus("Processing");
				buildInfo.Clear();
				buildInfo=null;
			}

			return true;
		}

        private void AddNewItem(IndexItem2 item, Hashtable buildInfo)
        {
            if (item != null)
            {
                if ((item.Title == "") && (item.Url == "") && (item.Children.Count == 0))
                {
                    return;
                }

                if ((item.Title == "") && (item.Url == "") && (item.Children.Count == 1))
                {
                    item = (IndexItem2)item.Children[0];
                }

                if (!buildInfo.ContainsKey(item.Title + ":" + item.Url))
                {
                    _indexKLinks.Add(item);
                    buildInfo.Add(item.Title + ":" + item.Url, item);
                }                
            }
        }

		private bool ReadBinaryTOC(IFileInfo fileInfo)
		{			
			if (fileInfo==null)
				return false;

			OnStatus("Extracting Binary TOC File");
			MemoryStream memStream=_currentWrapper.Extract(fileInfo);						
			OnStatus("Processing Binary TOC File");
			BinaryReaderEx binReader=null;

			_toc=new ArrayList();			
			TOCItem item=null;
			TOCItem CurrentParent=null;            
			try
			{								
				binReader=new BinaryReaderEx(memStream);
				binReader.Seek(0x30,SeekOrigin.Begin);
			
				int Count=1;                
				ArrayList s=new ArrayList();	

				int Parent=-1;
				while (!binReader.EOF)
				{									
					OnProgress(binReader.PercentageRead);
                    long Pos=binReader.Position;

					UInt32 data1=binReader.ReadUInt32();	// index pointer to Topic (For Name & URL)
					UInt32 data2=binReader.ReadUInt32();	// 4 bytes
					UInt32 data3=binReader.ReadUInt32();	// 4 bytes
					UInt32 data4=binReader.ReadUInt32();	// Flags 0x19 - Has Children, 0x10 - No Children
					UInt32 data5=binReader.ReadUInt32();	// 4 bytes
					UInt32 data6=binReader.ReadUInt32();	// Index pointer to first child if we have children,
					UInt32 data7=binReader.ReadUInt32();	// 4 bytes
					UInt32 data8=binReader.ReadUInt32();	// 4 bytes

                    // Trace.WriteLine(String.Format("0x{0} = 0x{1} 0x{2} 0x{3} 0x{4} 0x{5} 0x{6} 0x{7} 0x{8}", Pos.ToString("x04"), data1.ToString("x04"), data2.ToString("x04"), data3.ToString("x04"), data4.ToString("x04"), data5.ToString("x04"), data6.ToString("x04"), data7.ToString("x04"), data8.ToString("x04")));
					try
					{				            
                        if ((data1!=0) && (TopicHash.ContainsKey(data1)))
						{
                            HXTopic t = (HXTopic)TopicHash[data1];                            
							if (t!=null)
							{								                                
								if (t.Word1>0)
								{
									string Title = t.Word1.ToString();
									string URL = t.Word2.ToString();
									if (StringsHash.ContainsKey(t.Word1))
										Title=StringsHash[t.Word1].ToString();
									if (URLStringsHash.ContainsKey(t.Word2))
										URL=URLStringsHash[t.Word2].ToString();
									
									if (Title=="0")
										continue;									
									item = new TOCItem(Title,URL, 0, "");
									item.Tag=Count;
									item.Parent=CurrentParent;
									item.AssociatedFile=this;

									if (CurrentParent==null)
									{
										_toc.Add(item);
									}
									else
									{
										CurrentParent.Children.Add(item);			
									}
								}
							}
						}
                        else
                        {
                            if (StringsHash.ContainsKey((UInt32)data2))
                            {																
                                item = new TOCItem(StringsHash[data2].ToString(), "", 0, "");

								if (item.Name=="0")
									continue;

                                item.Tag = Count;
                                item.Parent = CurrentParent;
                                item.AssociatedFile = this;
                                if (CurrentParent == null)
                                {
                                    _toc.Add(item);
                                }
                                else
                                {
                                    CurrentParent.Children.Add(item);
                                }
                            }
                        }
					}
					catch(Exception ex)
					{
						Debug.WriteLine(ex.Message);
					}
					
					if ((data4 & 0x09 )== 0x09)
					{	
						// Trace.Indent();
						s.Add(new DictionaryEntry(Count,CurrentParent));						
						Parent=Count;						
						CurrentParent=item;
					}
					if ((data4 & 0x10)==0x10)
					{						
						if (data6!=Parent)
						{
							// Move CurrentParent up tree untill we find parent=data6
							TOCItem _item=CurrentParent;
							while ((_item!=null) && (_item.Tag!=data6))
							{
								if (_item.Parent==null)
									break;
								_item=_item.Parent;
							}
							CurrentParent=_item;
						}
					}

					Count++;
				}							
			}
			catch(Exception ex)
			{
				Debug.WriteLine(ex.Message);
				// Debug.WriteLine(ex.StackTrace);
			}
			finally
			{
                if (binReader!=null)
				    binReader.Close();
                binReader = null;

                if (memStream!=null)
                    memStream.Close();
				memStream=null;			

				OnProgress(0);
			}
			return true;
		}

        private string TOCName = "";
        private bool ReadSystem(IFileInfo fileInfo)
        {
            if (fileInfo==null)
				return false;

			OnStatus("Extracting System file");
			MemoryStream memStream=_currentWrapper.Extract(fileInfo);						

			OnStatus("Processing System file");
			BinaryReaderEx binReader=null;

            string HXCTitle = "";
            string Title = "";
            try
            {
                binReader = new BinaryReaderEx(memStream);

                bool bQuit = false;
                Encoding encoder = new UTF8Encoding();	
                while (!binReader.EOF)
                {
                    Int32 code = 0;
                    try
                    {
                        code = binReader.ReadInt32();
                    }
                    catch(Exception ex)
                    {
                        Debug.WriteLine(ex.Message);
                        bQuit = true;
                    }
                    switch (code)
                    {
                        case 4 :
                            binReader.Seek(0x0c, SeekOrigin.Current);
                            HXCTitle = binReader.ExtractUTF16String(0, true, encoder);
                            binReader.Seek(0x04, SeekOrigin.Current);
                            Title = binReader.ExtractUTF16String(0, true, encoder);
                            binReader.Seek(0x08, SeekOrigin.Current);
                            break;
                        case 8:
                            TOCName=binReader.ExtractUTF16String(0,true,encoder);
                            bQuit = true;
                            break;
                    }
                    if (bQuit)
                        break;
                }
            }
            finally
            {
                binReader.Close();
                memStream.Close();
            }
            return true;
        }

		/// <summary>
		/// Read a IStorage file
		/// </summary>
		/// <param name="fname">filename</param>
		/// <param name="type">type of file</param>
		private void ReadFile(string fname, HXSFileType type)
		{			
			FileInfo fi=new FileInfo(fname);
			OnStatus("Reading "+fi.Name);

			HXSStream iw=null;
			iw=new HXSStream(fname,this);						
			_currentWrapper=iw;
			MemoryStream fileObject=null;			

			// Look for Binary Toc & KLinks
			// if either exist, we need to read Strings, URLTable, Topics, etc then TOC/KLinks
	
			if (m_onlySystemData)
				return;

			OnStatus("Processing...");
			string FileName=@"/$Index/K/$LEAVES";
			IFileInfo fiBinaryKLinks=iw.FindFile(FileName);
			
            FileName=@"/$Index/$System";
			IFileInfo fiSystem=iw.FindFile(FileName);
            ReadSystem(fiSystem);

			IFileInfo fiBinaryTOC=iw.FindFile(TOCName);
            
			if ((fiBinaryTOC!=null) || (fiBinaryKLinks!=null))
			{
				// Read Strings, URLTable, URLStrings & Topics files				
				OnStatus("Reading Strings");
				bool rc=ReadStrings();
				if (rc) 
				{					
					OnStatus("Reading URLStrings");
					rc=ReadURLStrings();
				}

				if (rc)
				{
					OnStatus("Reading Topics");
					rc=ReadTopics();
				}

				if (rc)
				{
					if (fiBinaryTOC!=null)
					{
						OnStatus("Reading Binary Table of Contents");
						ReadBinaryTOC(fiBinaryTOC);
					}


					if (fiBinaryKLinks!=null)
					{
						OnStatus("Reading Binary Index");
						ReadBinaryIndex(fiBinaryKLinks);
					}				
				}
			}

			// Read TOC & Parse
			if (_toc ==null)
			{
				IFileInfo HHCInfo=iw.FindFileByExt(".hxt");
				if (HHCInfo!=null)
				{
					OnStatus("Reading Table of Contents");
					fileObject = iw.Extract(HHCInfo);							
					if ((fileObject != null) && (fileObject.Length>0))
					{
						ASCIIEncoding ascii=new ASCIIEncoding();
						OnStatus("Reading Table of Contents file");
						string fileString =ascii.GetString(fileObject.ToArray(),0,(int)fileObject.Length);
						_toc = HHCParser.ParseHHC(fileString, this);
					}			
				}
			}
		
			// Read Index File
			if (_indexKLinks==null)
			{			
				IFileInfo HHKInfo=iw.FindFileByExt(".hxk");
				fileObject = iw.Extract(HHKInfo);
				if ((fileObject != null) && (fileObject.Length>0))
				{
					OnStatus("Reading Index");
					string fileString = this.TextEncoding.GetString(fileObject.ToArray(),0,(int)fileObject.Length);								
					fileObject.Close();
					_indexKLinks = HHKParser.ParseHHK(fileString, this);
					OnStatus("Sorting Index");
					_indexKLinks.Sort(new SortIndexItems());
					OnStatus("Processing...");
				}
			}
			
			_currentWrapper=null;
			OnStatus("Processing...");
		}

		/// <summary>
		/// Enumerates the files in the hxs storage and gets all files matching a given extension.
		/// </summary>
		/// <param name="extension">extension to return</param>
		/// <returns>Returns an arraylist of filenames or null if nothing found</returns>
		/// <remarks>On large HXSs, enumerations are very slow. Only use it if necessary !</remarks>
		internal ArrayList EnumFilesByExtension(string extension)
		{
			ArrayList arrRet = new ArrayList();

			HXSStream iw = null;
			if(_currentWrapper == null)
				iw = new HXSStream(_hxsFileName,this);
			else
				iw = _currentWrapper;

			// arrRet=iw.GetFileListByExtenstion(extension);
			
			if(arrRet.Count > 0)
				return arrRet;

			return null;
		}

		/// <summary>
		/// Searches an TOC entry using the local
		/// </summary>
		/// <param name="local">local to search</param>
		/// <returns>Returns the TOC item</returns>
		internal TOCItem GetTOCItemByLocal(string local)
		{
			return GetTOCItemByLocal(this.TOC, local);
		}

		/// <summary>
		/// Recursively searches an TOC entry using its local
		/// </summary>
		/// <param name="arrTOC">toc level list</param>
		/// <param name="local">local to search</param>
		/// <returns>Returns the TOC item</returns>
		private TOCItem GetTOCItemByLocal(ArrayList arrTOC, string local)
		{
			TOCItem ret = null;
			foreach(TOCItem curItem in arrTOC)
			{
				string scL = curItem.Local.ToLower();
				string sL = local.ToLower();

				while(scL[0]=='/') // delete prefixing '/'
					scL = scL.Substring(1);

				while(sL[0]=='/') // delete prefixing '/'
					sL = sL.Substring(1);

				if(scL == sL)
					return curItem;

				if(curItem.Children.Count > 0)
				{
					ret =  GetTOCItemByLocal(curItem.Children, local);
					if(ret != null)
						return ret;
				}
			}

			return ret;
		}

		/// <summary>
		/// Removes a TOCItem from the toc
		/// </summary>
		/// <param name="rem">item to remove</param>
		/// <returns>Returns true if removed</returns>
		internal bool RemoveTOCItem(TOCItem rem)
		{
			if(rem == null)
				return false;

			return RemoveTOCItem(this.TOC, rem);
		}

		/// <summary>
		/// Recursively searches a TOCItem and removes it if found
		/// </summary>
		/// <param name="arrTOC">toc level list</param>
		/// <param name="rem">item to remove</param>
		/// <returns>Returns true if removed</returns>
		private bool RemoveTOCItem(ArrayList arrTOC, TOCItem rem)
		{
			for(int i=0; i<arrTOC.Count;i++)
			{
				TOCItem curItem = arrTOC[i] as TOCItem;

				if(curItem == rem)
				{
					arrTOC.RemoveAt(i);
					return true;
				}

				if(curItem.Children.Count > 0)
				{
					bool bRem = RemoveTOCItem(curItem.Children, rem);
					if(bRem)
						return true;
				}
			}

			return false;
		}

		#region Internal properties
		/// <summary>
		/// Gets the current storage wrapper.
		/// </summary>
		/// <remarks>This property will return not null, if there are currently file read actions running !</remarks>
		internal HXSStream CurrentStorageWrapper
		{
			get { return _currentWrapper;}
		}
		#endregion		

        /// <summary>
        /// Gets a value indicating whether this instance can search.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance can search; otherwise, <c>false</c>.
        /// </value>
		public override bool CanSearch
		{
			get 
			{ 
				return true;
			}
		}
		
		/// <summary>
		/// Gets the full pathname of the file
		/// </summary>
		public string HXSFilePath
		{
			get { return _hxsFileName; }
		}

		/// <summary>
		/// Gets the full pathname of the chi-file
		/// The file name is zero-length if there is no chi-file
		/// </summary>
		public string HXIFilePath
		{
			get { return _hxiFileName; }
		}

		/// <summary>
		/// Forms an URL for the web browser
		/// </summary>
		/// <param name="local">local resource</param>
		/// <returns>a url for the web-browser</returns>
		internal string FormURL(string local)
		{
			if( (local.ToLower().IndexOf("http://") >= 0) ||
				(local.ToLower().IndexOf("https://") >= 0) ||
				(local.ToLower().IndexOf("mailto:") >= 0) ||
				(local.ToLower().IndexOf("ftp://") >= 0)  ||
				(local.ToLower().IndexOf("ms-its:") >= 0))				
				return local;

			return this.UrlPrefix + _hxsFileName + "::/" + local;
		}

		/// <summary>
		/// Implement IDisposable.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			// This object will be cleaned up by the Dispose method.
			// Therefore, you should call GC.SupressFinalize to
			// take this object off the finalization queue 
			// and prevent finalization code for this object
			// from executing a second time.
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Dispose(bool disposing) executes in two distinct scenarios. 
		/// If disposing equals true, the method has been called directly 
		/// or indirectly by a user's code. Managed and unmanaged resources 
		/// can be disposed. 
		/// If disposing equals false, the method has been called by the 
		/// runtime from inside the finalizer and you should not reference  
		/// other objects. Only unmanaged resources can be disposed.
		/// </summary>
		/// <param name="disposing">disposing flag</param>
		private void Dispose(bool disposing)
		{
			// Check to see if Dispose has already been called.
			if(!this.disposed)
			{
				// If disposing equals true, dispose all managed 
				// and unmanaged resources.
				if(disposing)
				{
					// Dispose managed resources.
					_toc.Clear();
					_indexKLinks.Clear();
					_indexALinks.Clear();					

					if(_hxsFileInfo != null)
						_hxsFileInfo = null;
				}
			}
			disposed = true;         
		}
	}

	internal class HXTopic
	{
        public UInt32 Word1 = 0;
        public UInt32 Word2 = 0;
        private Hashtable StringsHash = null;
        private Hashtable URLStringsHash = null;
        public HXTopic(UInt32 _Word1, UInt32 _Word2, Hashtable _StringsHash, Hashtable _URLStringsHash)
		{
			Word1=_Word1;
			Word2=_Word2;
            StringsHash = _StringsHash;
            URLStringsHash = _URLStringsHash;
		}

        public override string ToString()
        {            
            string Title="";
            string URL="";
            if (StringsHash.ContainsKey(Word1))
                Title = StringsHash[Word1].ToString();
            else
                Title = Word1.ToString();
            if (URLStringsHash.ContainsKey(Word2))
                URL=URLStringsHash[Word2].ToString();
            else
                URL = Word2.ToString();

            return Title + " : " + URL;
        }
	}
}
